// -*- c -*-
//
// %CopyrightBegin%
//
// Copyright Ericsson AB 2020-2024. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
// %CopyrightEnd%
//

// Rewrites call_fun2 as call_fun: we're not yet helped by the Tag parameter,
// and can't do anything clever with Func either.
gen.call_fun2(Tag, Arity, Func) {
    BeamOp *call;

    (void)Tag;

    $NewBeamOp(S, call);
    $BeamOpNameArity(call, call_fun, 1);
    call->a[0] = Arity;
    call->next = NULL;

    /* Move the fun in place when needed. We don't generate this at the moment,
     * but a future version of the compiler might keep Func in a Y register. */
    if (Func.type != TAG_x || Arity.val != Func.val) {
        BeamOp *move;

        $NewBeamOp(S, move);
        $BeamOpNameArity(move, move, 2);
        move->a[0] = Func;
        move->a[1].type = TAG_x;
        move->a[1].val = Arity.val;
        move->next = call;

        return move;
    }

    return call;
}

// Generate the fastest instruction to fetch an integer from a binary.
gen.get_integer2(Fail, Ms, Live, Size, Unit, Flags, Dst) {
    BeamOp* op;
    UWord bits;

    $NewBeamOp(S, op);
    $NativeEndian(Flags);
    if (Size.type == TAG_i) {
        if (!beam_load_safe_mul(Size.val, Unit.val, &bits)) {
            goto error;
        } else if ((Flags.val & BSF_SIGNED) != 0) {
            goto generic;
        } else if (bits == 8) {
            $BeamOpNameArity(op, i_bs_get_integer_8, 3);
            op->a[0] = Ms;
            op->a[1] = Fail;
            op->a[2] = Dst;
        } else if (bits == 16 && (Flags.val & BSF_LITTLE) == 0) {
            $BeamOpNameArity(op, i_bs_get_integer_16, 3);
            op->a[0] = Ms;
            op->a[1] = Fail;
            op->a[2] = Dst;
#ifdef ARCH_64
        } else if (bits == 32 && (Flags.val & BSF_LITTLE) == 0) {
            $BeamOpNameArity(op, i_bs_get_integer_32, 3);
            op->a[0] = Ms;
            op->a[1] = Fail;
            op->a[2] = Dst;
#endif
        } else {
        generic:
            if (bits < SMALL_BITS) {
                $BeamOpNameArity(op, i_bs_get_integer_small_imm, 5);
                op->a[0] = Ms;
                op->a[1].type = TAG_u;
                op->a[1].val = bits;
                op->a[2] = Fail;
                op->a[3] = Flags;
                op->a[4] = Dst;
            } else {
                $BeamOpNameArity(op, i_bs_get_integer_imm, 6);
                op->a[0] = Ms;
                op->a[1].type = TAG_u;
                op->a[1].val = bits;
                op->a[2] = Live;
                op->a[3] = Fail;
                op->a[4] = Flags;
                op->a[5] = Dst;
            }
        }
    } else if (Size.type == TAG_q) {
        Eterm big = beamfile_get_literal(&S->beam, Size.val);
        Uint bigval;

        if (!term_to_Uint(big, &bigval)) {
        error:
            $BeamOpNameArity(op, jump, 1);
            op->a[0] = Fail;
        } else {
            if (!beam_load_safe_mul(bigval, Unit.val, &bits)) {
                goto error;
            }
            goto generic;
        }
    } else if (Size.type == TAG_x || Size.type == TAG_y) {
        $BeamOpNameArity(op, i_bs_get_integer, 6);
        op->a[0] = Ms;
        op->a[1] = Fail;
        op->a[2] = Live;
        op->a[3].type = TAG_u;
        op->a[3].val = (Unit.val << 3) | Flags.val;
        op->a[4] = Size;
        op->a[5] = Dst;
        return op;
    } else {
        /* Invalid literal size. */
        goto error;
    }
    return op;
}

gen.jump_tab(Src, Fail, Size, Rest) {
    Sint min, max;
    Sint i;
    Sint size;
    Sint arity;
    int fixed_args;
    BeamOp* op;

    ASSERT(Size.val >= 2 && Size.val % 2 == 0);

    /* Don't generate a jump table if there's only one choice */
    if (Size.val == 2) {
        BeamOp* jump;

        $NewBeamOp(S, op);
        $BeamOpNameArity(op, is_ne_exact, 3);
        op->a[0] = Rest[1];
        op->a[1] = Src;
        op->a[2] = Rest[0];

        $NewBeamOp(S, jump);
        $BeamOpNameArity(jump, jump, 1);
        jump->a[0] = Fail;

        op->next = jump;
        jump->next = NULL;
        return op;
    }

    /* Calculate the minimum and maximum values and size of jump table. */
    ASSERT(Rest[0].type == TAG_i);
    min = max = Rest[0].val;
    for (i = 2; i < Size.val; i += 2) {
        ASSERT(Rest[i].type == TAG_i && Rest[i+1].type == TAG_f);
        if (Rest[i].val < min) {
            min = Rest[i].val;
        } else if (max < Rest[i].val) {
            max = Rest[i].val;
        }
    }
    size = max - min + 1;

    /* Allocate structure and fill in the fixed fields. */
    $NewBeamOp(S, op);
    op->next = NULL;
    if (min == 0) {
        $BeamOpNameArity(op, i_jump_on_val_zero, 3);
    } else {
        $BeamOpNameArity(op, i_jump_on_val, 4);
    }
    fixed_args = op->arity;
    arity = fixed_args + size;
    $BeamOpArity(op, arity);
    op->a[0] = Src;
    op->a[1] = Fail;
    op->a[2].type = TAG_u;
    op->a[2].val = size;
    op->a[3].type = TAG_u;
    op->a[3].val = min;

    /* Fill in the jump table. */
    for (i = fixed_args; i < arity; i++) {
        op->a[i] = Fail;
    }

    for (i = 0; i < Size.val; i += 2) {
        Sint index = fixed_args + Rest[i].val - min;
        ASSERT(fixed_args <= index && index < arity);
        op->a[index] = Rest[i+1];
    }

    return op;
}

//
// Generate a select_val instruction.  We know that a jump table
// is not suitable, and that all values are of the same type
// (integer or atoms).
//
gen.select_val(Src, Fail, Size, Rest) {
    BeamOp* op;
    BeamOpArg *tmp;
    int arity = Size.val + 3;
    int size = Size.val / 2;
    int i, j, align = 0;

    if (size == 2) {
        /*
         * Use a special-cased instruction if there are only two values.
         */

        $NewBeamOp(S, op);
        op->next = NULL;
        $BeamOpNameArity(op, i_select_val2, 4);
        $BeamOpArity(op, arity - 1);
        op->a[0] = Src;
        op->a[1] = Fail;
        op->a[2] = Rest[0];
        op->a[3] = Rest[2];
        op->a[4] = Rest[1];
        op->a[5] = Rest[3];

        return op;
    }

    if (size <= 10) {
        /* Use linear search. Reserve place for a sentinel. */
        align = 1;
    }

    arity += 2*align;
    size  += align;

    $NewBeamOp(S, op);
    op->next = NULL;
    if (align == 0) {
        $BeamOpNameArity(op, i_select_val_bins, 3);
    } else {
        $BeamOpNameArity(op, i_select_val_lins, 3);
    }
    $BeamOpArity(op, arity);
    op->a[0] = Src;
    op->a[1] = Fail;
    op->a[2].type = TAG_u;
    op->a[2].val = size;

    tmp = (BeamOpArg *) erts_alloc(ERTS_ALC_T_LOADER_TMP, sizeof(BeamOpArg)*(arity-2*align));

    for (i = 3; i < arity - 2*align; i++) {
        tmp[i-3] = Rest[i-3];
    }

    /* Sort the values to make them useful for a binary or sentinel search. */
    beam_load_sort_select_vals(tmp, size - align);

    j = 3;
    for (i = 3; i < arity - 2*align; i += 2) {
        op->a[j]      = tmp[i-3];
        op->a[j+size] = tmp[i-2];
        j++;
    }

    erts_free(ERTS_ALC_T_LOADER_TMP, (void *) tmp);

    if (align) {
        /* Add sentinel for linear search. */
        op->a[j].type = TAG_u;
        op->a[j].val  = ~((BeamInstr)0);
        op->a[j+size] = Fail;
    }

#ifdef DEBUG
    for (i = 0; i < size - 1; i++) {
        ASSERT(op->a[i+3].val <= op->a[i+4].val);
    }
#endif

    return op;
}

//
// Generate a select_val instruction for big numbers.
//
gen.select_literals(Src, Fail, Size, Rest) {
    BeamOp* op;
    BeamOp* jump;
    BeamOp** prev_next = &op;

    int i;

    for (i = 0; i < Size.val; i += 2) {
        BeamOp* op;
        ASSERT(Rest[i].type == TAG_q);

        $NewBeamOp(S, op);
        $BeamOpNameArity(op, is_ne_exact, 3);
        op->a[0] = Rest[i+1];
        op->a[1] = Src;
        op->a[2] = Rest[i];
        *prev_next = op;
        prev_next = &op->next;
    }

    $NewBeamOp(S, jump);
    $BeamOpNameArity(jump, jump, 1);
    jump->next = NULL;
    jump->a[0] = Fail;
    *prev_next = jump;
    return op;
}

//
// Replace a select_val instruction with a constant controlling expression
// with a jump instruction.
//
gen.const_select_val(Src, Fail, Size, Rest) {
    BeamOp* op;
    int i;

    ASSERT(Size.type == TAG_u);

    $NewBeamOp(S, op);
    $BeamOpNameArity(op, jump, 1);
    op->next = NULL;

    /* Search for a literal matching the controlling expression. */
    switch (Src.type) {
    case TAG_q:
        {
            Eterm expr = beamfile_get_literal(&S->beam, Src.val);
            for (i = 0; i < Size.val; i += 2) {
                if (Rest[i].type == TAG_q) {
                    Eterm term = beamfile_get_literal(&S->beam, Rest[i].val);
                    if (eq(term, expr)) {
                        ASSERT(Rest[i+1].type == TAG_f);
                        op->a[0] = Rest[i+1];
                        return op;
                    }
                }
            }
        }
        break;
    case TAG_i:
    case TAG_a:
        for (i = 0; i < Size.val; i += 2) {
            if (Rest[i].val == Src.val && Rest[i].type == Src.type) {
                ASSERT(Rest[i+1].type == TAG_f);
                op->a[0] = Rest[i+1];
                return op;
            }
        }
        break;
    }

    /*
     * No match.  Use the failure label.
     */

    op->a[0] = Fail;
    return op;
}

//
// Split a list consisting of both small and bignumbers into two
// select_val instructions.
//
gen.split_values(Src, TypeFail, Fail, Size, Rest) {
    BeamOp* op1;
    BeamOp* op2;
    BeamOp* label;
    BeamOp* is_integer;
    int i;

    ASSERT(Size.val >= 2 && Size.val % 2 == 0);

    $NewBeamOp(S, is_integer);
    $BeamOpNameArity(is_integer, is_integer, 2);
    is_integer->a[0] = TypeFail;
    is_integer->a[1] = Src;

    $NewBeamOp(S, label);
    $BeamOpNameArity(label, label, 1);
    label->a[0].type = TAG_u;
    label->a[0].val = beam_load_new_label(S);

    $NewBeamOp(S, op1);
    $BeamOpNameArity(op1, select_val, 3);
    $BeamOpArity(op1, 3 + Size.val);
    op1->a[0] = Src;
    op1->a[1].type = TAG_f;
    op1->a[1].val = label->a[0].val;
    op1->a[2].type = TAG_u;
    op1->a[2].val = 0;

    $NewBeamOp(S, op2);
    $BeamOpNameArity(op2, select_val, 3);
    $BeamOpArity(op2, 3 + Size.val);
    op2->a[0] = Src;
    op2->a[1] = Fail;
    op2->a[2].type = TAG_u;
    op2->a[2].val = 0;

    /* Split the list. */
    ASSERT(Size.type == TAG_u);
    for (i = 0; i < Size.val; i += 2) {
        BeamOp* op = (Rest[i].type == TAG_q) ? op2 : op1;
        int dst = 3 + op->a[2].val;

        ASSERT(Rest[i+1].type == TAG_f);
        op->a[dst] = Rest[i];
        op->a[dst+1] = Rest[i+1];
        op->arity += 2;
        op->a[2].val += 2;
    }
    ASSERT(op1->a[2].val > 0);
    ASSERT(op2->a[2].val > 0);

    /* Order the instruction sequence appropriately. */
    if (TypeFail.val == Fail.val) {
        /*
         * select_val L1 S ... (small numbers)
         * label L1
         * is_integer Fail S
         * select_val Fail S ... (bignums)
         */
        op1->next = label;
        label->next = is_integer;
        is_integer->next = op2;
    } else {
        /*
         * is_integer TypeFail S
         * select_val L1 S ... (small numbers)
         * label L1
         * select_val Fail S ... (bignums)
         */
        is_integer->next = op1;
        op1->next = label;
        label->next = op2;
        op1 = is_integer;
    }
    op2->next = NULL;

    return op1;
}


//
// Tag the list of values with tuple arity tags.
//
gen.select_tuple_arity(Src, Fail, Size, Rest) {
    BeamOp* op;
    BeamOpArg *tmp;
    int arity = Size.val + 3;
    int size = Size.val / 2;
    int i, j, align = 0;

    /* Verify the validity of the list. */

    if (Size.val % 2 != 0) {
        return NULL;
    }

    for (i = 0; i < Size.val; i += 2) {
        if (Rest[i].type != TAG_u || Rest[i+1].type != TAG_f) {
            return NULL;
        }
    }

    /*
     * Use a special-cased instruction if there are only two values.
     */
    if (size == 2) {
        $NewBeamOp(S, op);
        $BeamOpNameArity(op, i_select_tuple_arity2, 4);
        $BeamOpArity(op, arity - 1);
        op->next = NULL;
        op->a[0] = Src;
        op->a[1] = Fail;
        op->a[2].type = TAG_u;
        op->a[2].val  = Rest[0].val;
        op->a[3].type = TAG_u;
        op->a[3].val  = Rest[2].val;
        op->a[4] = Rest[1];
        op->a[5] = Rest[3];

        return op;
    }

    /*
     * Generate the generic instruction.
     * Assumption:
     *   Few different tuple arities to select on (fewer than 20).
     *   Use linear scan approach.
     */

    align = 1;

    arity += 2*align;
    size  += align;

    $NewBeamOp(S, op);
    $BeamOpNameArity(op, i_select_tuple_arity, 3);
    $BeamOpArity(op, arity);
    op->next = NULL;
    op->a[0] = Src;
    op->a[1] = Fail;
    op->a[2].type = TAG_u;
    op->a[2].val = size;

    tmp = (BeamOpArg *) erts_alloc(ERTS_ALC_T_LOADER_TMP, sizeof(BeamOpArg)*(arity-2*align));

    for (i = 3; i < arity - 2*align; i+=2) {
        tmp[i-3].type = TAG_v;
        tmp[i-3].val  = make_arityval_unchecked(Rest[i-3].val);
        tmp[i-2]      = Rest[i-2];
    }

    /* Sort the values to make them useful for a binary or sentinel search. */
    beam_load_sort_select_vals(tmp, size - align);

    j = 3;
    for (i = 3; i < arity - 2*align; i += 2) {
        op->a[j]        = tmp[i-3];
        op->a[j + size] = tmp[i-2];
        j++;
    }

    erts_free(ERTS_ALC_T_LOADER_TMP, (void *) tmp);

    op->a[j].type = TAG_u;
    op->a[j].val  = ~((BeamInstr)0);
    op->a[j+size] = Fail;

    return op;
}

gen.new_small_map_lit(Dst, Live, Size, Rest) {
    unsigned size = Size.val;
    Uint lit;
    unsigned i;
    BeamOp* op;
    BeamOpArg* dst;
    Eterm* tmp;
    Eterm* thp;
    Eterm keys;

    $NewBeamOp(S, op);
    $BeamOpNameArity(op, i_new_small_map_lit, 3);
    $BeamOpArity(op, 3 + size/2);
    op->next = NULL;

    tmp = thp = erts_alloc(ERTS_ALC_T_LOADER_TMP, ((size == 0 ? 0 : 1) + size/2) * sizeof(*tmp));
    if (size == 0) {
        keys = erts_get_global_literal(ERTS_LIT_EMPTY_TUPLE);
    } else {
        keys = make_tuple(thp);
        *thp++ = make_arityval(size/2);
    }

    dst = op->a+3;

    for (i = 0; i < size; i += 2) {
        switch (Rest[i].type) {
        case TAG_a:
            *thp++ = Rest[i].val;
            ASSERT(is_atom(Rest[i].val));
            break;
        case TAG_i:
            *thp++ = make_small(Rest[i].val);
            break;
        case TAG_n:
            *thp++ = NIL;
            break;
        case TAG_q:
            *thp++ = beamfile_get_literal(&S->beam, Rest[i].val);
            break;
        }
        *dst++ = Rest[i + 1];
    }

    lit = beamfile_add_literal(&S->beam, keys, 1);
    erts_free(ERTS_ALC_T_LOADER_TMP, tmp);

    op->a[0] = Dst;
    op->a[1] = Live;
    op->a[2].type = TAG_q;
    op->a[2].val = lit;

    return op;
}

// Macro for generating a timeout instruction for a literal timeout value.
gen_literal_timeout(stp, fail, time, succ_instr, fail_instr) {
    BeamOp* op;
    Sint timeout;

    $NewBeamOp($stp, op);
    $BeamOpNameArity(op, $succ_instr, 2);
    op->a[0].type = TAG_u;
    op->a[1] = $fail;

    if ($time.type == TAG_i && (timeout = $time.val) >= 0 &&
#if defined(ARCH_64)
        (timeout >> 32) == 0
#else
        1
#endif
        ) {
        op->a[0].val = timeout;
#if !defined(ARCH_64)
    } else if ($time.type == TAG_q) {
        Eterm big = beamfile_get_literal(&S->beam, $time.val);

        if (is_not_big(big)) {
            goto error;
        }

        if (big_arity(big) > 1 || big_sign(big)) {
            goto error;
        } else {
            Uint u;
            (void) term_to_Uint(big, &u);
            op->a[0].val = (BeamInstr) u;
        }
#endif
    } else {
#if !defined(ARCH_64)
    error:
#endif
        $BeamOpNameArity(op, $fail_instr, 0);
    }
    return op;
}

gen.literal_timeout(Fail, Time) {
    $gen_literal_timeout(S, Fail, Time, wait_timeout_unlocked_int, i_wait_error);
}

gen.literal_timeout_locked(Fail, Time) {
    $gen_literal_timeout(S, Fail, Time, wait_timeout_locked_int, i_wait_error_locked);
}

// Generate an instruction for element/2.
gen.element(Fail, Index, Tuple, Dst) {
    BeamOp* op;

    $NewBeamOp(S, op);
    op->next = NULL;

    if (Index.type == TAG_i && Index.val > 0 &&
        Index.val <= ERTS_MAX_TUPLE_SIZE &&
        (Tuple.type == TAG_x || Tuple.type == TAG_y)) {
        $BeamOpNameArity(op, i_fast_element, 4);
        op->a[0] = Tuple;
        op->a[1] = Fail;
        op->a[2].type = TAG_u;
        op->a[2].val = Index.val;
        op->a[3] = Dst;
    } else {
        $BeamOpNameArity(op, i_element, 4);
        op->a[0] = Tuple;
        op->a[1] = Fail;
        op->a[2] = Index;
        op->a[3] = Dst;
    }

    return op;
}

// Generate the fastest instruction to fetch a binary from a binary.
gen.get_binary2(Fail, Ms, Live, Size, Unit, Flags, Dst) {
    BeamOp* op;

    $NewBeamOp(S, op);
    $NativeEndian(Flags);

    if (Size.type == TAG_a && Size.val == am_all) {
        $BeamOpNameArity(op, i_bs_get_binary_all2, 5);
        op->a[0] = Ms;
        op->a[1] = Fail;
        op->a[2] = Live;
        op->a[3] = Unit;
        op->a[4] = Dst;
    } else if (Size.type == TAG_i) {
        $BeamOpNameArity(op, i_bs_get_binary_imm2, 6);
        op->a[0] = Ms;
        op->a[1] = Fail;
        op->a[2] = Live;
        op->a[3].type = TAG_u;
        if (!beam_load_safe_mul(Size.val, Unit.val, &op->a[3].val)) {
            goto error;
        }
        op->a[4] = Flags;
        op->a[5] = Dst;
    } else if (Size.type == TAG_q) {
        Eterm big = beamfile_get_literal(&S->beam, Size.val);
        Uint bigval;

        if (!term_to_Uint(big, &bigval)) {
        error:
            $BeamOpNameArity(op, jump, 1);
            op->a[0] = Fail;
        } else {
            $BeamOpNameArity(op, i_bs_get_binary_imm2, 6);
            op->a[0] = Ms;
            op->a[1] = Fail;
            op->a[2] = Live;
            op->a[3].type = TAG_u;
            if (!beam_load_safe_mul(bigval, Unit.val, &op->a[3].val)) {
                goto error;
            }
            op->a[4] = Flags;
            op->a[5] = Dst;
        }
    } else if (Size.type == TAG_x || Size.type == TAG_y) {
        $BeamOpNameArity(op, i_bs_get_binary2, 6);
        op->a[0] = Ms;
        op->a[1] = Fail;
        op->a[2] = Live;
        op->a[3] = Size;
        op->a[4].type = TAG_u;
        op->a[4].val = (Unit.val << 3) | Flags.val;
        op->a[5] = Dst;
    } else {
        /* Invalid literal size. */
        goto error;
    }
    op->next = NULL;
    return op;
}

gen.is_function2(Fail, Fun, Arity) {
    BeamOp* op;
    int literal_arity = Arity.type == TAG_i;
    int fun_is_reg = Fun.type == TAG_x || Fun.type == TAG_y;

    $NewBeamOp(S, op);

    if (fun_is_reg && literal_arity) {
        /*
         * Most common case. Fun in a register and arity
         * is an integer literal.
         */
        if (Arity.val > MAX_ARG) {
            /* Arity is negative or too big. */
            $BeamOpNameArity(op, jump, 1);
            op->a[0] = Fail;
            return op;
        } else {
            $BeamOpNameArity(op, hot_is_function2, 3);
            op->a[0] = Fail;
            op->a[1] = Fun;
            op->a[2].type = TAG_u;
            op->a[2].val = Arity.val;
            return op;
        }
    } else {
        /*
         * Handle extremely uncommon cases by a slower sequence.
         */
        BeamOp* move_fun;
        BeamOp* move_arity;

        $NewBeamOp(S, move_fun);
        $NewBeamOp(S, move_arity);

        move_fun->next = move_arity;
        move_arity->next = op;

        $BeamOpNameArity(move_fun, move, 2);
        move_fun->a[0] = Fun;
        move_fun->a[1].type = TAG_x;
        move_fun->a[1].val = 1022;

        $BeamOpNameArity(move_arity, move, 2);
        move_arity->a[0] = Arity;
        move_arity->a[1].type = TAG_x;
        move_arity->a[1].val = 1023;

        $BeamOpNameArity(op, cold_is_function2, 3);
        op->a[0] = Fail;
        op->a[1].type = TAG_x;
        op->a[1].val = 1022;
        op->a[2].type = TAG_x;
        op->a[2].val = 1023;
        return move_fun;
    }
}

INIT_YREGS(S, N) {
    int i;
    for (i = 0; i < $N; i++) {
	BeamOp* init;

	$NewBeamOp($S, init);
	$BeamOpNameArity(init, i_init, 1);
	init->a[0] = Yregs[i];
	*p = init;
	p = &init->next;
    }
}

gen.allocate(Ns, Live, N, Yregs) {
    BeamOp* alloc;
    BeamOp** p;

    $NewBeamOp(S, alloc);
    alloc->a[0] = Ns;
    alloc->a[1] = Live;

    if (Ns.val <= 2 * N.val) {
	/*
	 * At least half of the Y registers need explicit
	 * initialization. It will be cheaper to zero all Y registers.
	 */
	$BeamOpNameArity(alloc, i_allocate_zero, 2);
    } else {
	$BeamOpNameArity(alloc, allocate, 2);
	p = &alloc->next;
	$INIT_YREGS(S, N.val);
    }
    return alloc;
}

gen.allocate_heap(Ns, Nh, Live, N, Yregs) {
    BeamOp* alloc;
    BeamOp** p;

    $NewBeamOp(S, alloc);
    alloc->a[0] = Ns;
    alloc->a[1] = Nh;
    alloc->a[2] = Live;

    if (Ns.val <= 2 * N.val) {
	/*
	 * At least half of the Y registers need explicit
	 * initialization. It will be cheaper to zero all Y registers.
	 */
	$BeamOpNameArity(alloc, i_allocate_heap_zero, 3);
    } else {
	$BeamOpNameArity(alloc, allocate_heap, 3);
	p = &alloc->next;
	$INIT_YREGS(S, N.val);
    }
    return alloc;
}

gen.init_yregs(N, Yregs) {
    BeamOp* first = NULL;
    BeamOp** p;

    p = &first;
    $INIT_YREGS(S, N.val);
    return first;
}

gen.create_bin(Fail, Alloc, Live, Unit, Dst, N, Segments) {
    BeamOp* op;
    int fixed_args;
    BeamOpArg* src;
    BeamOpArg* dst;
    BeamOpArg* endp;

    endp = Segments + N.val;
    N.val = 5*N.val/6;

    $NewBeamOp(S, op);
    $BeamOpNameArity(op, i_bs_create_bin, 5);
    fixed_args = op->arity;
    $BeamOpArity(op, (N.val + fixed_args));

    op->a[0] = Fail;
    op->a[1] = Alloc;
    op->a[2] = Live;
    op->a[3] = Dst;
    op->a[4] = N;

    for (src = Segments, dst = op->a+fixed_args; src < endp; src += 6, dst += 5) {
        UWord unit;
        BeamOpArg Flags;
        Uint flags = 0;
        BeamOpArg Size;
        Uint type;
        Uint segment;

        ASSERT(src[0].type = TAG_a);
        ASSERT(src[1].type == TAG_u);
        ASSERT(src[2].type == TAG_u);
        segment = src[1].val;

        /* Get unit. */
        dst[1] = src[2];
        unit = dst[1].val;

        /* Translate flags. */
        Flags = src[3];              /* Flags */
        if (Flags.type != TAG_n) {
            if (Flags.type == TAG_q) {
                Eterm term = beamfile_get_literal(&S->beam, Flags.val);
                while (is_list(term)) {
                    Eterm* consp = list_val(term);
                    Eterm elem = CAR(consp);
                    switch (elem) {
                    case am_little:
                        flags |= BSF_LITTLE;
                        break;
                    case am_native:
                        flags |= BSF_NATIVE;
                        break;
                    }
                    term = CDR(consp);
                }
                ASSERT(is_nil(term));
            }
        }
        Flags.type = TAG_u;
        Flags.val = flags;
        $NativeEndian(Flags);
        Flags.val = (segment << 3) | Flags.val;
        dst[2] = Flags;

        /* Store source. */
        dst[3] = src[4];        /* Src */

        /* Get size */
        Size = src[5];          /* Size */

        /* Translate type. */
        switch (src[0].val) {
        case am_append:
            type = BSC_APPEND;
            break;
        case am_private_append:
            type = BSC_PRIVATE_APPEND;
            break;
        case am_binary:
            {
                UWord bits;
                type = BSC_BINARY;
                if (Size.type == TAG_a && Size.val == am_all) {
                    type = BSC_BINARY_ALL;
                } else if (Size.type == TAG_i &&
                           (Sint) Size.val >= 0 &&
                           beam_load_safe_mul(Size.val, unit, &bits) &&
                           (bits >> (sizeof(Uint)-1)*8) == 0) {
                    type = BSC_BINARY_FIXED_SIZE;
                    Size.type = TAG_u;
                    Size.val = bits;
                    unit = 0;
                }
            }
            break;
        case am_float:
            {
                UWord bits;
                type = BSC_FLOAT;
                if (Size.type == TAG_i &&
                    (Sint) Size.val >= 0 &&
                    beam_load_safe_mul(Size.val, unit, &bits) &&
                    (bits >> (sizeof(Uint)-1)*8) == 0) {
                    type = BSC_FLOAT_FIXED_SIZE;
                    Size.type = TAG_u;
                    Size.val = bits;
                    unit = 0;
                }
            }
            break;
        case am_integer:
            {
                UWord bits;
                type = BSC_INTEGER;
                if (Size.type == TAG_i &&
                    (Sint) Size.val >= 0 &&
                    beam_load_safe_mul(Size.val, unit, &bits) &&
                    (bits >> (sizeof(Uint)-1)*8) == 0) {
                    type = BSC_INTEGER_FIXED_SIZE;
                    Size.type = TAG_u;
                    Size.val = bits;
                    unit = 0;
                }
            }
            break;
        case am_string:
            type = BSC_STRING;
            ASSERT(Size.type == TAG_i);
            ASSERT(unit == 8);
            Size.type = TAG_u;
            Size.val = Size.val; /* Size of string in bytes. */
            unit = 0;
            break;
        case am_utf8:
            type = BSC_UTF8;
            break;
        case am_utf16:
            type = BSC_UTF16;
            break;
        case am_utf32:
            type = BSC_UTF32;
            Size.type = TAG_u;
            Size.val = 32;
            break;
        default:
            abort();
        }
        dst[0].type = TAG_u;
        dst[0].val = type;

        /* Store value of unit. */
        dst[1].val = unit;

        /* Store size. */
        dst[4] = Size;
    }
    return op;
}

gen.update_record(Hint, Size, Src, Dst, N, Updates) {
    BeamOp *begin, *prev;
    Sint count, i;

    ASSERT(Hint.type == TAG_a);
    ASSERT(Size.type == TAG_u && Size.val < SCRATCH_X_REG);
    ASSERT(N.type == TAG_u && !(N.val % 2) && (N.val / 2) <= Size.val);

    $NewBeamOp(S, begin);
    if (Hint.val == am_inplace) {
        $BeamOpNameArity(begin, i_update_record_in_place, 5);
    } else {
        $BeamOpNameArity(begin, i_update_record_copy, 5);
    }

    begin->a[0] = Size;
    begin->a[1] = Src;
    begin->a[2] = Dst;
    begin->a[3] = Updates[0];
    begin->a[4] = Updates[1];

    count = N.val;
    prev = begin;

    for (i = 2; i < count; i += 2) {
        BeamOp *next;
        int same_reg;

        $NewBeamOp(S, next);
        $BeamOpNameArity(next, i_update_record_continue, 2);

        /* Encode the offset from the _end_ of the tuple so that we can act
         * relative to HTOP. */
        next->a[0].type = TAG_u;
        next->a[0].val = (Size.val + 1) - Updates[i].val;

        if (Updates[i + 1].type != Dst.type) {
            same_reg = 0;
        } else if (Dst.type == TAG_x || Dst.type == TAG_y) {
            /* We must not compare the type indices (if any). */
            same_reg = (Updates[i + 1].val & REG_MASK) == (Dst.val & REG_MASK);
        } else {
            same_reg = 1;
        }

        /* The first instruction overwrites the destination register after
         * stashing its contents to SCRATCH_X_REG, so all updates must be
         * rewritten accordingly. */
        if (same_reg) {
            next->a[1].type = TAG_x;
            next->a[1].val = SCRATCH_X_REG;
        } else {
            next->a[1] = Updates[i + 1];
        }

        next->next = NULL;
        prev->next = next;

        prev = next;
    }

    if (Hint.val == am_inplace) {
        BeamOp *next;

        $NewBeamOp(S, next);
        $BeamOpNameArity(next, i_update_record_in_place_done, 0);

        next->next = NULL;
        prev->next = next;
    }

    return begin;
}

gen.bs_match(Fail, Ctx, N, List) {
    BeamOp* first_op = 0;
    BeamOp** next_ptr = &first_op;
    BeamOp* test_heap_op = 0;
    BeamOp* read_op = 0;
#ifdef ARCH_64
    BeamOp* eq_op = 0;
#endif
    int src;

    src = 0;
    while (src < N.val) {
        Uint unit;
        Uint size;
        Uint words_needed;
        BeamOp* op;

        /* Calculate the number of heap words needed for this
         * instruction. */
        words_needed = 0;
        switch (List[src].val) {
        case am_binary:
            ASSERT(List[src+3].type == TAG_u);
            ASSERT(List[src+4].type == TAG_u);
            size = List[src+3].val * List[src+4].val;
            words_needed = erts_extracted_bitstring_size(size);
            break;
        case am_integer:
            ASSERT(List[src+3].type == TAG_u);
            ASSERT(List[src+4].type == TAG_u);
            size = List[src+3].val * List[src+4].val;
            if (size >= SMALL_BITS) {
                words_needed = BIG_NEED_FOR_BITS(size);
            }
            break;
        case am_get_tail:
            words_needed = BUILD_SUB_BITSTRING_HEAP_NEED;
            break;
        }

        /* Emit a test_heap instrution if needed and there is
         * no previous one. */
        if ((List[src].val == am_Eq || words_needed) && test_heap_op == 0 &&
            List[src+1].type == TAG_u) {
            $NewBeamOp(S, test_heap_op);
            $BeamOpNameArity(test_heap_op, test_heap, 2);

            test_heap_op->a[0].type = TAG_u;
            test_heap_op->a[0].val = 0; /* Number of heap words */
            test_heap_op->a[1] = List[src+1]; /* Live */

            *next_ptr = test_heap_op;
            next_ptr = &test_heap_op->next;
        }

        if (words_needed) {
            test_heap_op->a[0].val += words_needed;
        }

        /* Translate this sub-instruction to a BEAM instruction. */
        op = 0;
        switch (List[src].val) {
        case am_ensure_at_least: {
            Uint size = List[src+1].val;
            unit = List[src+2].val;
            if (size != 0 && unit == 1) {
                $NewBeamOp(S, op);
                $BeamOpNameArity(op, i_bs_ensure_bits, 3);
                op->a[0] = Ctx;
                op->a[1].type = TAG_u;
                op->a[1].val = size;
                op->a[2] = Fail;
            } else if (size != 0 && unit != 1) {
                $NewBeamOp(S, op);
                $BeamOpNameArity(op, i_bs_ensure_bits_unit, 4);

                op->a[0] = Ctx;
                op->a[1].type = TAG_u;
                op->a[1].val = size; /* Size */
                op->a[2].type = TAG_u;
                op->a[2].val = unit; /* Unit */
                op->a[3] = Fail;
            } else if (size == 0 && unit != 1) {
                $NewBeamOp(S, op);
                $BeamOpNameArity(op, bs_test_unit, 3);

                op->a[0] = Fail;
                op->a[1] = Ctx;
                op->a[2].type = TAG_u;
                op->a[2].val = unit;
            } else if (size == 0 && unit == 1) {
                /* This test is redundant because it always
                 * succeeds. This should only happen for unoptimized
                 * code. Generate a dummy instruction to ensure that
                 * we don't trigger the sanity check at the end of
                 * this generator. */
                $NewBeamOp(S, op);
                $BeamOpNameArity(op, delete_me, 0);
            }
            src += 3;
            break;
        }
        case am_ensure_exactly: {
            $NewBeamOp(S, op);
            $BeamOpNameArity(op, bs_test_tail2, 3);

            op->a[0] = Fail;
            op->a[1] = Ctx;
            op->a[2]= List[src+1]; /* Size */

            src += 2;
            break;
        }
        case am_binary: {
            ASSERT(List[src+3].type == TAG_u);
            ASSERT(List[src+4].type == TAG_u);
            size = List[src+3].val;
            unit = List[src+4].val;

            $NewBeamOp(S, op);
            $BeamOpNameArity(op, i_bs_get_fixed_binary, 3);

            op->a[0] = Ctx;
            op->a[1].type = TAG_u;
            op->a[1].val = size * unit; /* Size */
            op->a[2] = List[src+5]; /* Dst */

            read_op = 0;
            src += 6;
            break;
        }
        case am_integer: {
            Uint flags = 0;
            BeamOpArg Flags;

            /* Translate flags. */
            Flags = List[src+2];
            if (Flags.type == TAG_n) {
                Flags.type = TAG_u;
                Flags.val = 0;
            } else if (Flags.type == TAG_q) {
                Eterm term = beamfile_get_literal(&S->beam, Flags.val);
                while (is_list(term)) {
                    Eterm* consp = list_val(term);
                    Eterm elem = CAR(consp);
                    switch (elem) {
                    case am_little:
                        flags |= BSF_LITTLE;
                        break;
                    case am_native:
                        flags |= BSF_NATIVE;
                        break;
                    case am_signed:
                        flags |= BSF_SIGNED;
                        break;
                    }
                    term = CDR(consp);
                }
                ASSERT(is_nil(term));
                Flags.type = TAG_u;
                Flags.val = flags;
                $NativeEndian(Flags);
            }

            ASSERT(List[src+3].type == TAG_u);
            ASSERT(List[src+4].type == TAG_u);
            size = List[src+3].val * List[src+4].val;

#define READ_OP_SIZE 1
            if (size < SMALL_BITS && flags == 0) {
                /* This is a suitable segment -- an unsigned big
                 * endian integer that fits in a small. */
                if (read_op == 0 || read_op->a[READ_OP_SIZE].val + size > 8*sizeof(Uint)) {
                    /* There is either no previous i_bs_read_bits instruction or
                     * the size of this segment don't fit into it. */
                    $NewBeamOp(S, read_op);
                    $BeamOpNameArity(read_op, i_bs_read_bits, 2);

                    read_op->a[0] = Ctx;
                    read_op->a[1].type = TAG_u;
                    read_op->a[1].val = 0;

                    *next_ptr = read_op;
                    next_ptr = &read_op->next;
                }

                read_op->a[READ_OP_SIZE].val += size;

                $NewBeamOp(S, op);
                $BeamOpNameArity(op, i_bs_extract_integer, 2);
                op->a[0].type = TAG_u;
                op->a[0].val = size;
                op->a[1] = List[src+5]; /* Dst */
            } else {
                /* Little endian, signed, or might not fit in a small. */
                $NewBeamOp(S, op);
                $BeamOpNameArity(op, i_bs_get_fixed_integer, 4);

                op->a[0] = Ctx;
                op->a[1].type = TAG_u;
                op->a[1].val = size;    /* Size */
                op->a[2] = Flags;       /* Flags */
                op->a[3] = List[src+5]; /* Dst */

                read_op = 0;
            }

            src += 6;
            break;
        }
        case am_Eq: {
            ASSERT(List[src+2].type == TAG_u);
            ASSERT(List[src+3].type == TAG_u);
            size = List[src+2].val;

            if (read_op == 0 || read_op->a[READ_OP_SIZE].val + size > 8*sizeof(Uint)) {
                /* There is either no previous i_bs_read_bits instruction or
                 * the size of this segment don't fit into it. */
                $NewBeamOp(S, read_op);
                $BeamOpNameArity(read_op, i_bs_read_bits, 2);

                read_op->a[0] = Ctx;
                read_op->a[1].type = TAG_u;
                read_op->a[1].val = 0;

                *next_ptr = read_op;
                next_ptr = &read_op->next;
            }

            read_op->a[READ_OP_SIZE].val += size;

#ifdef ARCH_64
            if (eq_op &&
                eq_op->next == 0 && /* Previous instruction? */
                eq_op->a[1].val + size <= 8*sizeof(Uint)) {
                /* Coalesce with the previous `=:=` instruction. */
                eq_op->a[1].val += size;
                eq_op->a[2].val = eq_op->a[2].val << size | List[src+3].val;
            }
#else
            if (0) {
                ;
            }
#endif
            else {
                $NewBeamOp(S, op);
                $BeamOpNameArity(op, i_bs_eq, 3);

#ifdef ARCH_64
                eq_op = op;
#endif

                op->a[0] = Fail;
                op->a[1] = List[src+2]; /* Size */
                op->a[2] = List[src+3]; /* Value */
            }

            src += 4;
            break;
        }
        case am_get_tail:
            $NewBeamOp(S, op);
            $BeamOpNameArity(op, i_bs_get_tail, 2);

            op->a[0] = Ctx;
            op->a[1] = List[src+3]; /* Dst */

            read_op = 0;
            src += 4;
            break;
        case am_skip:
            ASSERT(List[src+1].type == TAG_u);
            size = List[src+1].val;

            $NewBeamOp(S, op);

            if (read_op && read_op->a[READ_OP_SIZE].val + size <= 8*sizeof(Uint)) {
                read_op->a[READ_OP_SIZE].val += size;
                $BeamOpNameArity(op, i_bs_drop, 1);
                op->a[0] = List[src+1]; /* Size */
            } else {
                $BeamOpNameArity(op, i_bs_skip, 2);
                op->a[0] = Ctx;
                op->a[1] = List[src+1]; /* Size */
                read_op = 0;
            }

            src += 2;
            break;
        default:
            /*
             * This is an unknown sub command. It was probably produced by a later
             * release of Erlang/OTP than the current one. Fail loading.
             */

            $NewBeamOp(S, op);
            $BeamOpNameArity(op, bad_bs_match, 1);
            op->a[0] = List[src];
            *next_ptr = op;
            return first_op;
        }

        if (op) {
            *next_ptr = op;
            next_ptr = &op->next;
        }
    }

    if (test_heap_op && test_heap_op->a[0].val == 0) {
        /* This test_heap instruction was forced by the `=:=` sub
         * instruction, but it turned out that no test_heap instruction was
         * needed. */
        $BeamOpNameArity(test_heap_op, delete_me, 0);
    }

    if (first_op == 0) {
        erts_exit(ERTS_ERROR_EXIT, "loading bs_match in %T:%T/%d: no instructions loaded",
                  S->module, S->function, S->arity);
    }

    ASSERT(first_op);
    return first_op;
}

gen_increment(stp, reg, val, dst) {
    BeamOp* op;
    $NewBeamOp($stp, op);
    $BeamOpNameArity(op, i_increment, 3);
    op->a[0] = $reg;
    op->a[1].type = TAG_u;
    op->a[1].val = $val;
    op->a[2] = $dst;
    return op;
}

gen.increment(Reg, Integer, Dst) {
    $gen_increment(S, Reg, Integer.val, Dst);
}

gen.increment_from_minus(Reg, Integer, Dst) {
    $gen_increment(S, Reg, -Integer.val, Dst);
}

gen.plus_from_minus(Fail, Live, Src, Integer, Dst) {
    BeamOp* op;

    ASSERT(Integer.type == TAG_i && IS_SSMALL(-(Sint)Integer.val));

    $NewBeamOp(S, op);
    $BeamOpNameArity(op, gen_plus, 5);
    op->a[0] = Fail;
    op->a[1] = Live;
    op->a[2] = Src;
    op->a[3].type = TAG_i;
    op->a[3].val = -(Sint)Integer.val;
    op->a[4] = Dst;

    return op;
}
